#!/usr/bin/env python3
# -*- encoding: utf8 -*-
import ast, glob, os, re, numpy as np
from PIL import Image
from io import BytesIO
from skimage import io
from img_process.morphology_scale import morphology_scale

img_dir = os.path.join(os.path.dirname(__file__), '../data/components/')

available_components = [ os.path.splitext(os.path.basename(filename))[0]
    for filename in glob.glob(os.path.join(img_dir, '*.png')) ]

# get the image of the corresponding input
def get_comp_img(hanzi):
    return (io.imread(os.path.join(img_dir,
        '{}.png'.format(hanzi))) > 0).astype(np.uint8)

# get an image of the composed components
# component_array example: [
#     [ '弓', [ 0, 0 ], [ 255, 90 ] ],
#     [ '长', [ 20, 110 ], [ 240, 255 ] ],
# ]
# returns a numpy array
# the component_array should be matrix-wise indexed
def get_composed(component_array, w=256, h=256):
    result = np.zeros((w, h), dtype=np.uint8)
    n_composed = len(component_array)
    for component in component_array:
        char = component[0]
        # top left and bottom right
        tl = component[1]; br = component[2]
        try:
            char_img = get_comp_img(char)
        except:
            print(char + ' not found')
            char_img = np.zeros((256, 256), dtype=np.uint8)
        try:
            scaled = morphology_scale(
                char_img, (br[0] - tl[0], br[1] - tl[1]),
                mix=0.1, width_adjustment='fitting_target',
                target_stroke_width=(25 if n_composed <= 2 else 20))
        except Exception as e:
            print(e)
        result[tl[0]:br[0],tl[1]:br[1]] += scaled
    return (result == 0).astype(np.uint8)

# PART 2  Server program
import flask
from flask import Flask
from urllib.parse import urljoin

# the REST service of the glyph composing functionality
bp = flask.Blueprint('glyph_combining', __name__,
    static_url_path='/', static_folder='../static')

# convert a numpy array to a PIL image
def array2image(arr):
    return Image.fromarray(np.uint8(arr * 255), mode='L')

def serve_pil_image(pil_img):
    img_io = BytesIO()
    pil_img.save(img_io, 'PNG')
    img_io.seek(0)
    return flask.send_file(img_io, mimetype='image/png')

# test if the expression (URI path) is in valid format (see below)
def is_valid(expression):
    return re.match(
        r'(@(\w|[0-9a-fA-F]{4,5})(,\d+){4})+$', expression) is not None

# the composing expression should be in the format:
# @char0,imin0,jmin0,imax0,jmax0@char1,imin1,jmin1,imax1,jmax1
def to_composing_elements(expression):
    str_groups = expression.split('@')[1:]
    splitted_groups = [ string.split(',') for string in str_groups ]
    decode = lambda x: ast.literal_eval(
        "u'\\U{}'".format(x.lower().rjust(8, '0')))
    composing_elements = [ [
        group[0] if len(group[0]) == 1 else decode(group[0]),
        [ int(group[1]), int(group[2]) ], [ int(group[3]), int(group[4]) ] ]
        for group in splitted_groups ]
    return composing_elements

# glyph composing API
@bp.route('/compose/<expression>', methods=['GET'])
def serve_compose(expression):
    if is_valid(expression):
        elems = (to_composing_elements(expression))
        img = get_composed(elems, 512, 512)
        return serve_pil_image(array2image(img)), 200
    else:
        return '404 NOT FOUND: "{}" is an invalid expression'\
            .format(expression), 404

# return an empty image for empty request
white_image = array2image(np.ones((512, 512), dtype=np.uint8))
@bp.route('/compose', methods=['GET'])
@bp.route('/compose/', methods=['GET'])
def serve_empty():
    return serve_pil_image(white_image), 200

# the available components stored in data/components
@bp.route('/available-components.json', methods=['GET'])
def get_available():
    return flask.jsonify(available_components), 200

# root redirection
@bp.route('/', methods=['GET'])
def root():
    return bp.send_static_file('index.html')

# the web service, note the url_prefix
app = Flask(__name__)
app.register_blueprint(bp, url_prefix='/seal-compose-demo/')
app.config['JSON_AS_ASCII'] = False

# serve when run this module directly
if __name__ == '__main__':
    app.run(debug=True)
